import pygame
from element_tree import *
from genshin_elements import *
from PygameTemplates.button import Button
from math import sin, cos, sqrt ,pi, floor
from powers import *
from cheaters import *





class App:
    # objects
    window: pygame.Surface
    clock: pygame.time.Clock
    pile: Pile
    exit_button: Button
    stack_rect: pygame.Rect
    elem_stack: ElemStack
    capa: ElemCapability
    power_selector: PowerSelector
    cheater: CheaterBase

    # game parameters
    radius: float
    outer_radius: float
    backgroud_color: tuple
    stack_cap: int
    pile_sidelength: int 
    pile_center: int
    difficulty_mult: float
    page_size: tuple
    capa_type: ELEMENT_TYPE
    frame_rate: int

    # mode flags
    running: bool = False    
    insert_mode: bool = True
    game_phase: GAMEPHASE

    def __init__(self, radius:float, outer_radius:float, backgroud_color = (135,196,237), 
            page_size=(1920,1080),full_screen=True, stack_cap=7, difficulty_mult = 2) -> None:
        # settings
        self.page_size = page_size
        self.backgroud_color = backgroud_color
        self.radius = radius
        self.outer_radius = outer_radius
        self.stack_cap = stack_cap
        self.difficulty_mult = difficulty_mult
        self.frame_rate = custum_settings['frame_rate']
        
        pygame.init()
        # objects
        #   window
        if(full_screen):
            self.window = pygame.display.set_mode(page_size, pygame.FULLSCREEN)
        else:
            self.window = pygame.display.set_mode(page_size)
        pygame.display.set_caption(custum_settings['version'])
        self.clock = pygame.time.Clock()
        self.pile = Pile()
        self.elem_stack = ElemStack(self.stack_cap)
        #   exit button
        self.exit_button = Button(page_size[0]-65, 5, 60, 30, message='EXIT', base_colour=(0,100,0), 
                    border_colour=backgroud_color, horizontal_alignment='center', font_colour=(255,255,255))
        #   stack rectangle
        self.stack_rect = pygame.Rect(15, page_size[1] - 25 - self.outer_radius*2, 
                (self.outer_radius*2 + 10)*self.stack_cap , self.outer_radius*2 + 10)
        #   pile region
        self.pile_sidelength = self.stack_rect.y - 2*self.outer_radius - 10
        self.pile_center = self.stack_rect.y/2
        #   power selector
        self.power_selector = PowerSelector(50, page_size[0] - 50, page_size[1]/2, self.radius)

        # flags
        self.running = True
        self.insert_mode = False
        self.has_rendered = False
        self.game_phase = GAMEPHASE.BEGIN
    
    def __del__(self):
        pygame.quit()

    def get_capa(self):
        pos = (self.page_size[0] - 2*self.outer_radius , 2*self.outer_radius + 50)
        radius = self.outer_radius

        if(self.capa_type == ELEMENT_TYPE.TIME):
            self.capa = TimePower(pos, radius)
        elif(self.capa_type == ELEMENT_TYPE.MOVO):
            self.capa = MovoPower(pos, radius, custum_settings['general_n_collect'])
        elif(self.capa_type == ELEMENT_TYPE.NINI):
            self.capa = NiniPower(pos, radius, custum_settings['general_n_collect'])
        elif(self.capa_type == ELEMENT_TYPE.SADA):
            self.capa = SadaPower(pos, radius, custum_settings['general_n_collect'])
        elif(self.capa_type == ELEMENT_TYPE.GUSHA):
            self.capa = GushaPower(pos, radius, custum_settings['gusha_n_collect'])

        if custum_settings['powers_initial_activate']:
            self.capa.activate()

    def generate(self):
        self.pile = Pile()
        self.elem_stack = ElemStack(self.stack_cap)
        # generate pile
        pos_list = helical_path((self.pile_center*2, self.pile_center), 2*pi/custum_settings['num_per_circ'], 1, 100,
                self.pile_sidelength/2 - 2*self.outer_radius,1 , self.outer_radius, True)
        pos_list += helical_path((self.pile_center*2, self.pile_center), 2*pi/custum_settings['num_per_circ'], 1, 100,
                self.pile_sidelength/2 - 2*self.outer_radius,1 , self.outer_radius, False)
        self.generate_from_list(pos_list)
        
        # cheater
        self.cheater.initialize(self)

        # generate capability
        self.get_capa()

    def generate_from_list(self, pos_list):

        N_items = floor(len(pos_list)/3)
        gen = ElemGenerator(N_items, int(self.stack_cap * self.difficulty_mult))
        elem_type_list = gen.generate()
        for j in range(N_items*3):
            curr_node = GenshinElemNode(elem_type_list[j], pos_list[j], self.radius)
            self.pile.add_top(curr_node, overlap_cmp_fun)

    def result_check(self):
        if(not self.pile.all_nodes):
            self.game_phase = GAMEPHASE.WIN
            self.has_rendered = False
            return
        if(self.elem_stack.is_full()):
            self.game_phase = GAMEPHASE.LOSE
            self.has_rendered = False
            return

    def on_click_main(self,pos):

        if(self.insert_mode):
            elem_type = randint(ELEMENT_TYPE.MAX_ID)
            new_node = GenshinElemNode(elem_type, pos, self.radius)
            self.pile.add_top(new_node, overlap_cmp_fun)
        else:
            for node in self.pile.top_nodes:
                if(pos_on_elem(pos, node)):
                    self.pile.remove_top(node)
                    result = self.elem_stack.append(node.elem_type)
                    if(not self.capa.effective):
                        if(result >= 0):
                            # eliminated
                            self.capa.append(result)
                    break
        
        # capability
        if self.capa.active:
            if(pos_on_elem(pos, self.capa.elem_node)):
                self.capa.on_click()

    def event_call_begin(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            result = self.power_selector.on_click(event.pos, self)
            if(result >= 0):
                self.game_phase = GAMEPHASE.MAIN
                self.capa_type = result
                self.generate()
            return
        if event.type == pygame.KEYDOWN:
            self.power_selector.on_press(event)
       
    def event_call_main(self,event):
        event = self.cheater.event_call(event, self)

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                self.running = False
                return


        elif event.type == pygame.MOUSEBUTTONDOWN:
            self.on_click_main(event.pos)
            return
    
    def event_call_finish(self, event):
        if event.type == pygame.KEYDOWN:
            self.game_phase = GAMEPHASE.BEGIN
            self.power_selector.reinit()
            return

    def render_finish(self):
        if(not self.has_rendered):
            if self.game_phase == GAMEPHASE.WIN:
                id = randint(custum_settings['n_win_bg'])
                img_txt = pygame.image.load('./resources/pictures/win_text.png')
                img_bg = pygame.image.load('./resources/pictures/win_bg/%d.png'%(id))

            elif self.game_phase == GAMEPHASE.LOSE:
                id = randint(custum_settings['n_lose_bg'])
                img_txt = pygame.image.load('./resources/pictures/lose_text.png')
                img_bg = pygame.image.load('./resources/pictures/lose_bg/%d.png'%(id))

            rect = img_txt.get_rect(center = (self.page_size[0]/2, self.page_size[1]/2))
            self.window.blit(img_bg, rect)
            self.window.blit(img_txt, rect)
            pygame.display.update()
            self.has_rendered = True

    def render_main(self):
        self.window.fill(self.backgroud_color)
        pygame.draw.rect(self.window, color=(255,255,255),rect=self.stack_rect)

        for item in self.pile.all_nodes:
            rect = img_top[item.elem_type].get_rect(center = item.pos)
            if(item.is_top()):
                self.window.blit(img_top[item.elem_type], rect)
            else:
                self.window.blit(img_hidden[item.elem_type], rect)
        
        # element stack
        self.elem_stack.render(self.window, self.stack_rect.x + self.outer_radius + 5,
             2 * self.outer_radius + 10, self.stack_rect.y + self.stack_rect.height/2)

        # buttons
        self.exit_button.draw(self.window)

        # capability
        self.capa.render(self.window)

        # cheater
        self.cheater.render(self.window)

        pygame.display.update()

    def render_begin(self):
        self.window.fill(self.backgroud_color)
        self.power_selector.render(self.window)
        pygame.display.update()

    def render(self):
        if(self.game_phase == GAMEPHASE.MAIN):
            self.render_main()
        elif(self.game_phase == GAMEPHASE.WIN or self.game_phase == GAMEPHASE.LOSE):
            self.render_finish()
        elif(self.game_phase == GAMEPHASE.BEGIN):
            self.render_begin()
    
    def event_call(self, event):
        # common
        if event.type == pygame.QUIT:
            self.running = False
            return

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if(self.exit_button.click(event.pos)):
                self.running = False
                return

        if (self.game_phase == GAMEPHASE.BEGIN):
            self.event_call_begin(event)
        elif(self.game_phase == GAMEPHASE.MAIN):
            self.event_call_main(event)
        elif(self.game_phase == GAMEPHASE.WIN or self.game_phase == GAMEPHASE.LOSE):
            self.event_call_finish(event)

    def main_proc_main(self):
        self.result_check()
        self.cheater(self)
        self.capa(self)
    
    def main_proc_begin(self):
        self.power_selector()
    
    def main_proc_finish(self):
        pass

    def main_proc(self):
        if (self.game_phase == GAMEPHASE.BEGIN):
            self.main_proc_begin()
        elif(self.game_phase == GAMEPHASE.MAIN):
            self.main_proc_main()
        elif(self.game_phase == GAMEPHASE.WIN or self.game_phase == GAMEPHASE.LOSE):
            self.main_proc_finish()

    def run(self):
        while(self.running):
            for event in pygame.event.get():
                self.event_call(event)
            self.main_proc()
            self.render()
            self.clock.tick(self.frame_rate)

def helical_path(center, omega, v,min_r, max_r, ratio, rand_shift, outwards=True):
    if(outwards):
        r = min_r
    else:
        r = max_r
    theta = 0
    pos_list = []

    flag = True
    while( flag):
        x_shift = (rand()*2 - 1) * rand_shift
        y_shift = (rand()*2 - 1) * rand_shift
        pos = (center[0] + r*ratio * cos(theta)+x_shift, center[1] + r*sin(theta)+y_shift)
        pos_list.append(pos)
        if(outwards):
            r += v
            theta += omega
            flag = r < max_r
        else:
            r -= v
            theta -= omega
            flag = r > min_r

    return pos_list

if __name__ == '__main__':
    app = App(img_top[0].get_width()/2-2, img_top[0].get_width()/2, page_size=custum_settings['page_size'],
         full_screen=custum_settings['full_screen'], difficulty_mult=custum_settings['difficulty'])
    app.run()
